x86: Make x32 syscall support conditional on a kernel parameter
authorBen Hutchings <ben@decadent.org.uk>
Fri, 25 Jul 2014 00:16:15 +0000 (01:16 +0100)
committerBen Hutchings <ben@decadent.org.uk>
Thu, 17 Mar 2016 01:25:23 +0000 (01:25 +0000)
Enabling x32 in the standard amd64 kernel would increase its attack
surface while provide no benefit to the vast majority of its users.
No-one seems interested in regularly checking for vulnerabilities
specific to x32 (at least no-one with a white hat).

Still, adding another flavour just to turn on x32 seems wasteful.  And
the only differences on syscall entry are two instructions (mask out
the x32 flag and compare the syscall number).

So pad the standard comparison with a nop and add a kernel parameter
"syscall.x32" which controls whether this is replaced with the x32
version at boot time.  Add a Kconfig parameter to set the default.

Signed-off-by: Ben Hutchings <ben@decadent.org.uk>
Gbp-Pq: Topic features/x86
Gbp-Pq: Name x86-make-x32-syscall-support-conditional.patch

Documentation/kernel-parameters.txt
arch/x86/Kconfig
arch/x86/entry/entry_64.S
arch/x86/entry/syscall_64.c
arch/x86/include/asm/elf.h

index 30690908c6c8d6707c6f918f5d1797da096c3ef0..535180224b94b0e4706e74944a71ac6f978802bd 100644 (file)
@@ -3650,6 +3650,10 @@ bytes respectively. Such letter suffixes can also be entirely omitted.
 
        switches=       [HW,M68k]
 
+       syscall.x32=    [KNL,x86_64] Enable/disable use of x32 syscalls on
+                       an x86_64 kernel where CONFIG_X86_X32 is enabled.
+                       Default depends on CONFIG_X86_X32_DISABLED.
+
        sysfs.deprecated=0|1 [KNL]
                        Enable/disable old style sysfs layout for old udev
                        on older distributions. When this option is enabled
index db3622f22b618303a8bc9c3c2f34762f400dbafb..708648c350fcc7f9474f85b448556d2ae071218c 100644 (file)
@@ -2629,6 +2629,14 @@ config X86_X32
          elf32_x86_64 support enabled to compile a kernel with this
          option set.
 
+config X86_X32_DISABLED
+       bool "x32 ABI disabled by default"
+       depends on X86_X32
+       default n
+       help
+         Disable the x32 ABI unless explicitly enabled using the
+         kernel paramter "syscall.x32=y".
+
 config COMPAT
        def_bool y
        depends on IA32_EMULATION || X86_X32
index a55697d19824727fda8306c566bb99cb4ba6d980..b545e19c46e47e11efc1936fa5247e2363fdcacd 100644 (file)
@@ -177,8 +177,12 @@ entry_SYSCALL_64_fastpath:
 #if __SYSCALL_MASK == ~0
        cmpq    $__NR_syscall_max, %rax
 #else
-       andl    $__SYSCALL_MASK, %eax
-       cmpl    $__NR_syscall_max, %eax
+.global system_call_fast_compare
+.global system_call_fast_compare_end
+system_call_fast_compare:
+       cmpq    $511, %rax                      /* x32 syscalls start at 512 */
+       .byte   P6_NOP4
+system_call_fast_compare_end:
 #endif
        ja      1f                              /* return -ENOSYS (already in pt_regs->ax) */
        movq    %r10, %rcx
@@ -261,8 +265,12 @@ tracesys_phase2:
 #if __SYSCALL_MASK == ~0
        cmpq    $__NR_syscall_max, %rax
 #else
-       andl    $__SYSCALL_MASK, %eax
-       cmpl    $__NR_syscall_max, %eax
+.global system_call_trace_compare
+.global system_call_trace_compare_end
+system_call_trace_compare:
+       cmpq    $511, %rax                      /* x32 syscalls start at 512 */
+       .byte   P6_NOP4
+system_call_trace_compare_end:
 #endif
        ja      1f                              /* return -ENOSYS (already in pt_regs->ax) */
        movq    %r10, %rcx                      /* fixup for C */
@@ -356,6 +364,16 @@ opportunistic_sysret_failed:
 END(entry_SYSCALL_64)
 
 
+#if __SYSCALL_MASK != ~0
+       /* This replaces the usual comparisons if syscall.x32 is set */
+.global system_call_mask_compare
+.global system_call_mask_compare_end
+system_call_mask_compare:
+       andl    $__SYSCALL_MASK, %eax
+       cmpl    $__NR_syscall_max, %eax
+system_call_mask_compare_end:
+#endif
+
        .macro FORK_LIKE func
 ENTRY(stub_\func)
        SAVE_EXTRA_REGS 8
index 41283d22be7a92db23e46fb427593a2e878ee37d..1701739d7b178a524f7d0a5a4edb4691ec20ac6d 100644 (file)
@@ -3,8 +3,14 @@
 #include <linux/linkage.h>
 #include <linux/sys.h>
 #include <linux/cache.h>
+#include <linux/moduleparam.h>
+#undef MODULE_PARAM_PREFIX
+#define MODULE_PARAM_PREFIX "syscall."
+#include <linux/bug.h>
+#include <linux/init.h>
 #include <asm/asm-offsets.h>
 #include <asm/syscall.h>
+#include <asm/alternative.h>
 
 #define __SYSCALL_COMMON(nr, sym, compat) __SYSCALL_64(nr, sym, compat)
 
@@ -30,3 +36,40 @@ asmlinkage const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {
        [0 ... __NR_syscall_max] = &sys_ni_syscall,
 #include <asm/syscalls_64.h>
 };
+
+#ifdef CONFIG_X86_X32_ABI
+
+/* Maybe enable x32 syscalls */
+
+bool x32_enabled = !IS_ENABLED(CONFIG_X86_X32_DISABLED);
+module_param_named(x32, x32_enabled, bool, 0444);
+
+extern char system_call_fast_compare_end[], system_call_fast_compare[],
+       system_call_trace_compare_end[], system_call_trace_compare[],
+       system_call_mask_compare_end[], system_call_mask_compare[];
+
+static int __init x32_enable(void)
+{
+       BUG_ON(system_call_fast_compare_end - system_call_fast_compare != 10);
+       BUG_ON(system_call_trace_compare_end - system_call_trace_compare != 10);
+       BUG_ON(system_call_mask_compare_end - system_call_mask_compare != 10);
+
+       if (x32_enabled) {
+               text_poke_early(system_call_fast_compare,
+                               system_call_mask_compare, 10);
+               text_poke_early(system_call_trace_compare,
+                               system_call_mask_compare, 10);
+#ifdef CONFIG_X86_X32_DISABLED
+               pr_info("Enabled x32 syscalls\n");
+#endif
+       }
+#ifndef CONFIG_X86_X32_DISABLED
+       else
+               pr_info("Disabled x32 syscalls\n");
+#endif
+
+       return 0;
+}
+late_initcall(x32_enable);
+
+#endif
index 1514753fd43553e079696712b48a8d08b6966e98..2d9e42a14d699d4948537b97ea0c6c476ef04ebb 100644 (file)
@@ -154,6 +154,12 @@ do {                                               \
 
 #else /* CONFIG_X86_32 */
 
+#ifdef CONFIG_X86_X32_ABI
+extern bool x32_enabled;
+#else
+#define x32_enabled 0
+#endif
+
 /*
  * This is used to ensure we don't load something for the wrong architecture.
  */
@@ -162,7 +168,7 @@ do {                                                \
 
 #define compat_elf_check_arch(x)                                       \
        (elf_check_arch_ia32(x) ||                                      \
-        (IS_ENABLED(CONFIG_X86_X32_ABI) && (x)->e_machine == EM_X86_64))
+        (x32_enabled && (x)->e_machine == EM_X86_64))
 
 #if __USER32_DS != __USER_DS
 # error "The following code assumes __USER32_DS == __USER_DS"